#ifndef __MCS__SOURCE_LOCATION__HPP
#define __MCS__SOURCE_LOCATION__HPP

#include "String.hpp"

class SourcePosition;
class SourceLocation;

std::ostream & operator << (std::ostream & stream, SourcePosition const & cur);
std::ostream & operator<<(std::ostream & s, SourceLocation const & loc);

/// Abstract data type that represents position of the single character in the plain text document.
class SourcePosition : public Void
{
	friend std::ostream & operator << (std::ostream & stream, SourcePosition const & cur);
	friend std::ostream & operator<<(std::ostream & s, SourceLocation const & loc);
public:
	///Constructs null position.
	SourcePosition()
		: Void()
		, file_()
		, line_(0)
		, column_(0)
	{}
	
	///Constructs position object that represents beginning of \p file.
	SourcePosition(String const & file)
		: Void()
		, file_(file)
		, line_(1)
		, column_(1)
	{}

	///Constructs position object that represents position at specified \p column in \p line of \p file.
	SourcePosition(String const & file, int line, int column = 1)
		: Void()
		, file_(file)
		, line_(line)
		, column_(column)
	{}

	///Returns true if position object was constructed using null constructor.
	bool isNull() const { return file_.empty() && line_ == 0 && column_ == 0; }
	///Data integrity check.
	bool isValid() const { return (line_ > 0 && column_ > 0 && !file_.empty()) || isNull(); }

	String file() const { return file_; }
	void setFile(String const & file) { file_ = file; }

	int line() const { return line_; }
	void setLine(int line) { line_ = line; }
	int column() const { return column_; }
	void setColumn(int col) { column_ = col; }

	///Moves position one character left.
	void newChar() { ++column_; }
	///Moves position \p n characters left.
	void newChars(int n) { column_ += n; }
	///Moves position to the beginning of the next line.
	void newLine() { ++line_; column_ = 1; }

	///Moves position one character forward.
	void grow(char c) { if(c == '\n') newLine(); else newChar(); }

	SourcePosition next(char c) const { SourcePosition p(*this); p.grow(c); return p; }

	bool operator==(SourcePosition const & p) const { return column_ == p.column_ && line_ == p.line_ && file_ == p.file_;}
	bool operator!=(SourcePosition const & p) const { return column_ != p.column_ || line_ != p.line_ || file_ != p.file_;}

	int compare(SourcePosition const & p) const
	{
		int r = file_.compare(p.file_);
		if(r != 0) return r;
		if(line_ != p.line_)
			return line_ < p.line_ ? -1 : +1;
		if(column_ != p.column_)
			return column_ < p.column_ ? -1 : +1;
		return 0;
	}

	bool operator<(SourcePosition const & p) const { return compare(p) < 0; }
	bool operator>(SourcePosition const & p) const { return compare(p) > 0; }
	bool operator<=(SourcePosition const & p) const { return compare(p) <= 0; }
	bool operator>=(SourcePosition const & p) const { return compare(p) >= 0; }

	void swap(SourcePosition & p)
	{
		std::swap(file_, p.file_); 
		std::swap(line_, p.line_);
		std::swap(column_, p.column_);
	}
private:
	String file_;
	int line_;
	int column_;
};

inline std::ostream & operator << (std::ostream & stream, SourcePosition const & cur)
{
	return stream << cur.file_ << ':' << cur.line_ << '.' << cur.column_;
}

namespace std { inline void swap(SourcePosition & a, SourcePosition & b) { a.swap(b); } }

class SourceLocation : public Void
{
	friend std::ostream & operator<<(std::ostream & s, SourceLocation const & loc);
public:
	SourceLocation()
		: Void()
		, begin_()
		, end_()
	{}

	SourceLocation(String const & file)
		: Void()
		, begin_(file)
		, end_(file)
	{}

	SourceLocation(String const & file, int line, int col = 1)
		: Void()
		, begin_(file, line, col)
		, end_(file, line, col)
	{}

	SourceLocation(String const & file, int line1, int col1, int line2, int col2)
		: Void()
		, begin_(file, line1, col1)
		, end_(file, line2, col2)
	{}

	SourceLocation(SourcePosition const & p)
		: Void()
		, begin_(p)
		, end_(p)
	{}

	SourceLocation(SourcePosition const & b, SourcePosition const & e)
		: Void()
		, begin_(b)
		, end_(e)
	{}

	///Returns true if location was constructed with null constructor.
	bool isNull() const { return begin_.isNull() && end_.isNull(); }
	///Data integrity check.
	bool isValid() const { return begin_.isValid() && end_.isValid(); }
	///Returns true if location does not contains any characters.
	bool isEmpty() const { return begin_ < end_; }

	///Position of the first character in lovation.
	SourcePosition begin() const { return begin_; }
	///Position of the last character in location.
	SourcePosition end() const { return end_; }

	void setBegin(SourcePosition const & b) { begin_ = b; }
	void setEnd(SourcePosition const & e) { end_ = e; }

	void swap(SourceLocation & loc)
	{
		std::swap(begin_, loc.begin_);
		std::swap(end_, loc.end_);
	}
private:
	SourcePosition begin_;
	SourcePosition end_;
};

inline std::ostream & operator<<(std::ostream & stream, SourceLocation const & loc)
{
	stream << loc.begin_;
	if(loc.begin_.file_ != loc.end_.file_)
	{
		return stream << '-' << loc.end_;
	}
	if(loc.begin_.line_ != loc.end_.line_)
	{
		return stream << '-' << loc.end_.line_ << '.' << loc.end_.column_;
	}
	if(loc.begin_.column_ != loc.end_.column_)
	{
		return stream << '-' << loc.end_.column_;
	}
	return stream;
}

namespace std { inline void swap(SourceLocation & a, SourceLocation & b) { a.swap(b); } }

#endif //__MCS__SOURCE_LOCATION__HPP
